﻿using Microsoft.Office.Server.Search.Administration;
using Microsoft.Office.Server.Search.Administration.Query;
using Microsoft.Office.Server.Search.Query;
using Microsoft.Office.Server.Search.Query.Rules;
using Microsoft.Office.Server.Search.Query.Throttling;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Taxonomy;
using SPSDeploy.Extensions.Controllers;
using SPSDeploy.Extensions.Extensions;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Text;
using System.Xml.Linq;
using System.Xml.XPath;

namespace SPSDeploy.Extensions.Modules.Update
{
    /// <summary>
    /// Search update module
    /// </summary>
    internal class SearchModule : BaseModule
    {

        /// <summary>
        /// Initialization
        /// </summary>
        /// <param name="controller"></param>
        internal SearchModule(BaseController controller)
            : base(controller)
        {
        }

        /// <summary>
        /// Execute module
        /// </summary>
        /// <param name="application"></param>
        internal override void Execute(XElement application)
        {
            SearchServiceApplication serviceApplication = (SearchServiceApplication)ServiceApplicationExtension.GetServiceApplication(application.Attribute("name").Value, application.Attribute("type").Value);
            if (serviceApplication != null)
            {
                SearchService service = Microsoft.Office.Server.Search.Administration.SearchService.Service;
                Content content = new Content(serviceApplication);
                Ranking ranking = new Ranking(serviceApplication, SearchObjectOwner.EmptyOwner);
                FederationManager manager = new FederationManager(serviceApplication);
                UpdateSearchApplication(application, serviceApplication, service, content, ranking);
                UpdateSearchProxy(application, service);
                UpdateContentSources(application, content, serviceApplication);
                UpdateCrawlRules(application, content);
                UpdateCrawlMappings(application, content);
                UpdateFileTypes(application, content);
                UpdateCrawlerImpactRules(application, service);
                UpdateAuthoritativePages(application, ranking);
                UpdateDemotedSites(application, ranking);
                UpdateQuerySuggestions(application, serviceApplication);
                UpdateQueryClientTypes(application, serviceApplication);
                UpdateResultSources(application, manager, "searchServiceApplication");
                UpdateResultTypes(application, manager, new ResultItemTypeManager(serviceApplication), "searchServiceApplication");
                UpdateQueryRules(application, manager, new QueryRuleManager(serviceApplication), "searchServiceApplication");
                UpdateManagedProperties(application, serviceApplication);
                controller.OnProgressMessage(string.Format("Search service application '{0}' information updated.", application.Attribute("name").Value));
            }
        }

        #region Search Application
        /// <summary>
        /// Update search application
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="application"></param>
        /// <param name="service"></param>
        /// <param name="content"></param>
        /// <param name="ranking"></param>
        private void UpdateSearchApplication(XElement parent, SearchServiceApplication application, SearchService service, Content content, Ranking ranking)
        {
            bool serviceUpdated = false;
            bool applicationUpdated = false;
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/updates/update"))
                switch (element.Attribute("value").Value)
                {
                    case "defaultContentAccessAccount":
                        content.SetDefaultGatheringAccount(element.Attribute("to").Value, Tools.GetAccountPassword(element.Attribute("to").Value));
                        break;
                    case "contactEmail":
                        service.ContactEmail = element.Attribute("to").Value;
                        serviceUpdated = true;
                        break;
                    case "connectionTimeout":
                        service.ConnectionTimeout = Convert.ToInt32(element.Attribute("to").Value);
                        serviceUpdated = true;
                        break;
                    case "acknowledgementTimeout":
                        service.AcknowledgementTimeout = Convert.ToInt32(element.Attribute("to").Value);
                        serviceUpdated = true;
                        break;
                    case "ignoreSSLWarnings":
                        service.IgnoreSSLWarnings = Convert.ToBoolean(element.Attribute("to").Value);
                        serviceUpdated = true;
                        break;
                    case "alertsEnabled":
                        application.AlertsEnabled = Convert.ToBoolean(element.Attribute("to").Value);
                        applicationUpdated = true;
                        break;
                    case "queryLoggingEnabled":
                        application.QueryLogSettings.QLogEnabled = Convert.ToBoolean(element.Attribute("to").Value);
                        applicationUpdated = true;
                        break;
                    case "searchCenterUrl":
                        application.SearchCenterUrl = element.Attribute("to").Value;
                        applicationUpdated = true;
                        break;
                    case "querySuggestionsEnabled":
                        ranking.QuerySuggestionsEnabled = Convert.ToBoolean(element.Attribute("to").Value);
                        break;
                }
            if (serviceUpdated)
                service.Update();
            if (applicationUpdated)
                application.Update();
        }
        #endregion

        #region Search Proxy
        /// <summary>
        /// Update search proxy
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="service"></param>
        private void UpdateSearchProxy(XElement parent, SearchService service)
        {
            bool serviceUpdated = false;
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/proxy/updates/update"))
                switch (element.Attribute("value").Value)
                {
                    case "type":
                        service.ProxyType = (ProxyType)Enum.Parse(typeof(ProxyType), element.Attribute("to").Value);
                        serviceUpdated = true;
                        break;
                    case "address":
                        service.WebProxy.Address = string.IsNullOrEmpty(element.Attribute("to").Value) ? null : new Uri(element.Attribute("to").Value);
                        serviceUpdated = true;
                        break;
                    case "bypassOnLocal":
                        service.WebProxy.BypassProxyOnLocal = Convert.ToBoolean(element.Attribute("to").Value);
                        serviceUpdated = true;
                        break;
                    case "bypassList":
                        service.WebProxy.BypassArrayList.Clear();
                        if (!string.IsNullOrEmpty(element.Attribute("to").Value))
                            service.WebProxy.BypassArrayList.AddRange(element.Attribute("to").Value.Split(';'));
                        serviceUpdated = true;
                        break;
                    case "useForFederation":
                        service.UseCrawlProxyForFederation = Convert.ToBoolean(element.Attribute("to").Value);
                        serviceUpdated = true;
                        break;
                }
            if (serviceUpdated)
                service.Update();
        }
        #endregion

        #region Content Sources
        /// <summary>
        /// Update content sources
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="content"></param>
        /// <param name="application"></param>
        private void UpdateContentSources(XElement parent, Content content, SearchServiceApplication application)
        {
            bool updated = false;
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/contentSources/deletes/contentSource"))
            {
                content.ContentSources[element.Attribute("name").Value].Delete();
                updated = true;
            }
            if (updated)
            {
                content.ContentSources.Update();
                updated = false;
            }
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/contentSources/adds/contentSource"))
            {
                ContentSource item;
                switch (element.Attribute("type").Value)
                {
                    case "SharePoint":
                        item = content.ContentSources.Create(typeof(SharePointContentSource), (SharePointCrawlBehavior)Enum.Parse(typeof(SharePointCrawlBehavior), element.Attribute("crawlBehavior").Value), element.Attribute("name").Value);
                        ((SharePointContentSource)item).EnableContinuousCrawls = Convert.ToBoolean(element.Attribute("continuousCrawl").Value);
                        ((SharePointContentSource)item).FollowDirectories = Convert.ToBoolean(element.Attribute("followDirectories").Value);
                        break;
                    case "Web":
                        item = content.ContentSources.Create(typeof(WebContentSource), element.Attribute("name").Value);
                        ((WebContentSource)item).MaxPageEnumerationDepth = Convert.ToInt32(element.Attribute("maxPageDepth").Value);
                        ((WebContentSource)item).MaxSiteEnumerationDepth = Convert.ToInt32(element.Attribute("maxSiteDepth").Value);
                        break;
                    case "File":
                        item = content.ContentSources.Create(typeof(FileShareContentSource), element.Attribute("name").Value);
                        ((FileShareContentSource)item).FollowDirectories = Convert.ToBoolean(element.Attribute("followDirectories").Value);
                        break;
                    case "Exchange":
                        item = content.ContentSources.Create(typeof(ExchangePublicFolderContentSource), element.Attribute("name").Value);
                        ((ExchangePublicFolderContentSource)item).FollowDirectories = Convert.ToBoolean(element.Attribute("followDirectories").Value);
                        break;
                    case "Business":
                        item = content.ContentSources.Create(typeof(BusinessDataContentSource), element.Attribute("name").Value);
                        break;
                    case "Custom":
                        item = content.ContentSources.Create(typeof(CustomContentSource), element.Attribute("name").Value);
                        ((CustomContentSource)item).FollowDirectories = Convert.ToBoolean(element.Attribute("followDirectories").Value);
                        ((CustomContentSource)item).MaxPageEnumerationDepth = Convert.ToInt32(element.Attribute("maxPageDepth").Value);
                        ((CustomContentSource)item).MaxSiteEnumerationDepth = Convert.ToInt32(element.Attribute("maxSiteDepth").Value);
                        break;
                    default:
                        throw new Exception(string.Format("Content source type '{0}' is not supported.", element.Attribute("type").Value));
                }
                item.CrawlPriority = (CrawlPriority)Enum.Parse(typeof(CrawlPriority), element.Attribute("crawlPriority").Value);
                SetContentSourceStartAddresses(element, item.StartAddresses);
                item.IncrementalCrawlSchedule = GetContentSourceSchedule(element.XPathSelectElement("incrementalCrawlSchedule"), application);
                item.FullCrawlSchedule = GetContentSourceSchedule(element.XPathSelectElement("fullCrawlSchedule"), application);
                item.Update();
                updated = true;
            }
            if (updated)
            {
                content.ContentSources.Update();
                updated = false;
            }
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/contentSources/updates/contentSource"))
            {
                ContentSource item = content.ContentSources[element.Attribute("name").Value];
                foreach (XElement updateElement in element.XPathSelectElements("updates/update"))
                    switch (updateElement.Attribute("value").Value)
                    {
                        case "crawlPriority":
                            item.CrawlPriority = (CrawlPriority)Enum.Parse(typeof(CrawlPriority), updateElement.Attribute("to").Value);
                            updated = true;
                            break;
                        case "incrementalCrawlSchedule":
                            item.IncrementalCrawlSchedule = GetContentSourceSchedule(updateElement.XPathSelectElement("incrementalCrawlSchedule"), application);
                            updated = true;
                            break;
                        case "fullCrawlSchedule":
                            item.FullCrawlSchedule = GetContentSourceSchedule(updateElement.XPathSelectElement("fullCrawlSchedule"), application);
                            updated = true;
                            break;
                        case "startAddresses":
                            SetContentSourceStartAddresses(updateElement, item.StartAddresses);
                            updated = true;
                            break;
                        case "continuousCrawl":
                            ((SharePointContentSource)item).EnableContinuousCrawls = Convert.ToBoolean(updateElement.Attribute("to").Value);
                            updated = true;
                            break;
                        case "followDirectories":
                            if (item is SharePointContentSource)
                                ((SharePointContentSource)item).FollowDirectories = Convert.ToBoolean(updateElement.Attribute("to").Value);
                            else if (item is CustomContentSource)
                                ((CustomContentSource)item).FollowDirectories = Convert.ToBoolean(updateElement.Attribute("to").Value);
                            else if (item is HierarchicalContentSource)
                                ((HierarchicalContentSource)item).FollowDirectories = Convert.ToBoolean(updateElement.Attribute("to").Value);
                            updated = true;
                            break;
                        case "maxPageDepth":
                            if (item is WebContentSource)
                                ((WebContentSource)item).MaxPageEnumerationDepth = Convert.ToInt32(updateElement.Attribute("to").Value);
                            else if (item is CustomContentSource)
                                ((CustomContentSource)item).MaxPageEnumerationDepth = Convert.ToInt32(updateElement.Attribute("to").Value);
                            updated = true;
                            break;
                        case "maxSiteDepth":
                            if (item is WebContentSource)
                                ((WebContentSource)item).MaxSiteEnumerationDepth = Convert.ToInt32(updateElement.Attribute("to").Value);
                            else if (item is CustomContentSource)
                                ((CustomContentSource)item).MaxSiteEnumerationDepth = Convert.ToInt32(updateElement.Attribute("to").Value);
                            updated = true;
                            break;
                    }
                item.Update();
            }
            if (updated)
            {
                content.ContentSources.Update();
                updated = false;
            }
        }

        /// <summary>
        /// Set content source start addresses
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="startAddresses"></param>
        private void SetContentSourceStartAddresses(XElement parent, StartAddressCollection startAddresses)
        {
            startAddresses.Clear();
            foreach (XElement element in parent.XPathSelectElements("startAddresses/startAddress"))
                startAddresses.Add(new Uri(element.Value));
        }

        /// <summary>
        /// Return content source schedule
        /// </summary>
        /// <param name="element"></param>
        /// <param name="application"></param>
        /// <returns></returns>
        private Schedule GetContentSourceSchedule(XElement element, SearchServiceApplication application)
        {
            if (element.Attribute("type").Value == "None")
                return null;
            Schedule result;
            switch (element.Attribute("type").Value)
            {
                case "Daily":
                    result = new DailySchedule(application) { DaysInterval = Convert.ToInt32(element.Attribute("daysInterval").Value) };
                    break;
                case "Weekly":
                    result = new WeeklySchedule(application) { WeeksInterval = Convert.ToInt32(element.Attribute("weeksInterval").Value), DaysOfWeek = (DaysOfWeek)Enum.Parse(typeof(DaysOfWeek), element.Attribute("daysOfWeek").Value) };
                    break;
                case "MonthlyDate":
                    result = new MonthlyDateSchedule(application) { DaysOfMonth = (DaysOfMonth)Enum.Parse(typeof(DaysOfMonth), element.Attribute("daysOfMonth").Value), MonthsOfYear = (MonthsOfYear)Enum.Parse(typeof(MonthsOfYear), element.Attribute("monthsOfYear").Value) };
                    break;
                case "MonthlyDayOfWeek":
                    result = new MonthlyDayOfWeekSchedule(application) { DaysOfWeek = (DaysOfWeek)Enum.Parse(typeof(DaysOfWeek), element.Attribute("daysOfWeek").Value), WeeksOfMonth = (WeeksOfMonth)Enum.Parse(typeof(WeeksOfMonth), element.Attribute("weeksOfMonth").Value), MonthsOfYear = (MonthsOfYear)Enum.Parse(typeof(MonthsOfYear), element.Attribute("monthsOfYear").Value) };
                    break;
                default:
                    throw new Exception(string.Format("Content source schedule type '{0}' is not supported.", element.Attribute("type").Value));
            }
            DateTime begin = DateTime.ParseExact(element.Attribute("begin").Value, "yyyy-MM-dd", CultureInfo.InvariantCulture);
            DateTime start = DateTime.ParseExact(element.Attribute("start").Value, "HH:mm", CultureInfo.InvariantCulture);
            result.BeginYear = begin.Year;
            result.BeginMonth = begin.Month;
            result.BeginDay = begin.Day;
            result.StartHour = start.Hour;
            result.StartMinute = start.Minute;
            result.RepeatInterval = Convert.ToInt32(element.Attribute("repeatInterval").Value);
            result.RepeatDuration = Convert.ToInt32(element.Attribute("repeatDuration").Value);
            return result;
        }
        #endregion

        #region Crawl Rules
        /// <summary>
        /// Update crawl rules
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="content"></param>
        private void UpdateCrawlRules(XElement parent, Content content)
        {
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/crawlRules/deletes/crawlRule"))
                content.CrawlRules[element.Attribute("path").Value].Delete();
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/crawlRules/adds/crawlRule"))
            {
                CrawlRule item = content.CrawlRules.Create((CrawlRuleType)Enum.Parse(typeof(CrawlRuleType), element.Attribute("type").Value), Convert.ToBoolean(element.Attribute("regularExpression").Value), element.Attribute("path").Value);
                item.SuppressIndexing = Convert.ToBoolean(element.Attribute("suppressIndexing").Value);
                item.FollowComplexUrls = Convert.ToBoolean(element.Attribute("followComplexUrls").Value);
                item.CrawlAsHttp = Convert.ToBoolean(element.Attribute("crawlAsHttp").Value);
                SetCrawlRuleAuthentication(item, element.Attribute("authenticationType").Value, element.Attribute("accountName").Value);
                item.Update();
                content.CrawlRules.SetPriority(item, Convert.ToInt32(element.Attribute("priority").Value));
            }
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/crawlRules/updates/crawlRule"))
            {
                CrawlRule item = content.CrawlRules[element.Attribute("path").Value];
                foreach (XElement updateElement in element.XPathSelectElements("updates/update"))
                    switch (updateElement.Attribute("value").Value)
                    {
                        case "type":
                            item.Type = (CrawlRuleType)Enum.Parse(typeof(CrawlRuleType), updateElement.Attribute("to").Value);
                            break;
                        case "regularExpression":
                            item.IsAdvancedRegularExpression = Convert.ToBoolean(updateElement.Attribute("to").Value);
                            break;
                        case "suppressIndexing":
                            item.SuppressIndexing = Convert.ToBoolean(updateElement.Attribute("to").Value);
                            break;
                        case "followComplexUrls":
                            item.FollowComplexUrls = Convert.ToBoolean(updateElement.Attribute("to").Value);
                            break;
                        case "crawlAsHttp":
                            item.CrawlAsHttp = Convert.ToBoolean(updateElement.Attribute("to").Value);
                            break;
                        case "priority":
                            content.CrawlRules.SetPriority(item, Convert.ToInt32(updateElement.Attribute("to").Value));
                            break;
                        case "authenticationType":
                            SetCrawlRuleAuthentication(item, updateElement.Attribute("to").Value, element.XPathSelectElement("updates/update[@value='accountName']").Attribute("to").Value);
                            break;
                    }
                item.Update();
            }
        }

        /// <summary>
        /// Set crawl rule authentication
        /// </summary>
        /// <param name="crawlRule"></param>
        /// <param name="authenticationType"></param>
        /// <param name="accountName"></param>
        private void SetCrawlRuleAuthentication(CrawlRule crawlRule, string authenticationType, string accountName)
        {
            switch ((CrawlRuleAuthenticationType)Enum.Parse(typeof(CrawlRuleAuthenticationType), authenticationType))
            {
                case CrawlRuleAuthenticationType.DefaultRuleAccess:
                    crawlRule.SetCredentials(CrawlRuleAuthenticationType.DefaultRuleAccess, string.Empty, new SecureString());
                    break;
                case CrawlRuleAuthenticationType.AnonymousAccess:
                    crawlRule.SetCredentials(CrawlRuleAuthenticationType.AnonymousAccess, string.Empty, new SecureString());
                    break;
                case CrawlRuleAuthenticationType.NTLMAccountRuleAccess:
                    crawlRule.SetCredentials(CrawlRuleAuthenticationType.NTLMAccountRuleAccess, accountName, Tools.GetAccountPassword(accountName));
                    break;
                case CrawlRuleAuthenticationType.BasicAccountRuleAccess:
                    crawlRule.SetCredentials(CrawlRuleAuthenticationType.BasicAccountRuleAccess, accountName, Tools.GetAccountPassword(accountName));
                    break;
                case CrawlRuleAuthenticationType.CertificateRuleAccess:
                    crawlRule.SetCredentials(CrawlRuleAuthenticationType.CertificateRuleAccess, accountName, new SecureString());
                    break;
                default:
                    throw new Exception(string.Format("Crawl rule authentication type '{0}' is not supported.", authenticationType));
            }
        }
        #endregion

        #region Crawl Mappings
        /// <summary>
        /// Update crawl mappings
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="content"></param>
        private void UpdateCrawlMappings(XElement parent, Content content)
        {
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/crawlMappings/deletes/crawlMapping"))
                content.CrawlMappings[new Uri(element.Attribute("source").Value)].Delete();
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/crawlMappings/adds/crawlMapping"))
                content.CrawlMappings.Create(new Uri(element.Attribute("source").Value), new Uri(element.Attribute("target").Value));
        }
        #endregion

        #region File Types
        /// <summary>
        /// Update file types
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="content"></param>
        private void UpdateFileTypes(XElement parent, Content content)
        {
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/fileTypes/deletes/fileType"))
                content.ExtensionList[element.Attribute("extension").Value].Delete();
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/fileTypes/adds/fileType"))
                content.ExtensionList.Create(element.Attribute("extension").Value);
        }
        #endregion

        #region Crawler Impact Rules
        /// <summary>
        /// Update crawler impact rules
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="service"></param>
        private void UpdateCrawlerImpactRules(XElement parent, SearchService service)
        {
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/crawlImpactRules/deletes/crawlImpactRule"))
                service.SiteHitRules[element.Attribute("site").Value].Delete();
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/crawlImpactRules/adds/crawlImpactRule"))
                service.SiteHitRules.Create(element.Attribute("site").Value, Convert.ToInt32(element.Attribute("hitRate").Value), (SiteHitRuleBehavior)Enum.Parse(typeof(SiteHitRuleBehavior), element.Attribute("behavior").Value));
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/crawlImpactRules/updates/crawlImpactRule"))
            {
                SiteHitRule item = service.SiteHitRules[element.Attribute("site").Value];
                foreach (XElement updateElement in element.XPathSelectElements("updates/update"))
                    switch (updateElement.Attribute("value").Value)
                    {
                        case "behavior":
                            item.Behavior = (SiteHitRuleBehavior)Enum.Parse(typeof(SiteHitRuleBehavior), updateElement.Attribute("to").Value);
                            break;
                        case "hitRate":
                            item.HitRate = Convert.ToInt32(updateElement.Attribute("to").Value);
                            break;
                    }
                item.Update();
            }
        }
        #endregion

        #region Authoritative Pages
        /// <summary>
        /// Update authoritative pages
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="ranking"></param>
        private void UpdateAuthoritativePages(XElement parent, Ranking ranking)
        {
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/authorityPages/deletes/authorityPage"))
                ranking.AuthorityPages[new Uri(element.Attribute("url").Value)].Delete();
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/authorityPages/adds/authorityPage"))
                ranking.AuthorityPages.Create(new Uri(element.Attribute("url").Value), Convert.ToSingle(element.Attribute("level").Value));
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/authorityPages/updates/authorityPage"))
            {
                AuthorityPage item = ranking.AuthorityPages[new Uri(element.Attribute("url").Value)];
                foreach (XElement updateElement in element.XPathSelectElements("updates/update"))
                    switch (updateElement.Attribute("value").Value)
                    {
                        case "level":
                            ranking.AuthorityPages[new Uri(element.Attribute("url").Value)].Delete();
                            ranking.AuthorityPages.Create(new Uri(element.Attribute("url").Value), Convert.ToSingle(updateElement.Attribute("to").Value));
                            break;
                    }
            }
        }

        /// <summary>
        /// Update demoted sites
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="ranking"></param>
        private void UpdateDemotedSites(XElement parent, Ranking ranking)
        {
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/demotedSites/deletes/demotedSite"))
                ranking.DemotedSites[new Uri(element.Attribute("url").Value)].Delete();
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/demotedSites/adds/demotedSite"))
                ranking.DemotedSites.Create(new Uri(element.Attribute("url").Value));
        }
        #endregion

        #region Query Suggestions
        /// <summary>
        /// Update query suggestions
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="application"></param>
        private void UpdateQuerySuggestions(XElement parent, SearchServiceApplication application)
        {
            LanguageResourcePhrase item;
            List<LanguageResourcePhrase> list = QuerySuggestionsExtension.GetAll(application);
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/querySuggestions/deletes/querySuggestion"))
                if ((item = list.Where(i => QuerySuggestionsExtension.Equals(element, i)).FirstOrDefault()) != null)
                {
                    item.Delete();
                    list.Remove(item);
                }
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/querySuggestions/adds/querySuggestion"))
                QuerySuggestionsExtension.Create(element, application);
        }
        #endregion

        #region Query Client Types
        /// <summary>
        /// Update query client types
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="application"></param>
        private void UpdateQueryClientTypes(XElement parent, SearchServiceApplication application)
        {
            SearchObjectOwner owner = new SearchObjectOwner(SearchObjectLevel.Ssa);
            QueryClientTypeManager manager = new QueryClientTypeManager(application);
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/queryClientTypes/deletes/queryClientType"))
                manager.DeleteQueryClientTypeMapping(manager.GetQueryClientTypeMapping(element.Attribute("name").Value, owner));
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/queryClientTypes/adds/queryClientType"))
                manager.CommitQueryClientTypeMapping(new QueryClientTypeMapping(element.Attribute("name").Value, (QueryThrottlingTier)Enum.Parse(typeof(QueryThrottlingTier), element.Attribute("throttlingTier").Value), owner));
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/queryClientTypes/updates/queryClientType"))
            {
                QueryClientTypeMapping item = manager.GetQueryClientTypeMapping(element.Attribute("name").Value, owner);
                foreach (XElement updateElement in element.XPathSelectElements("updates/update"))
                    switch (updateElement.Attribute("value").Value)
                    {
                        case "throttlingTier":
                            item.ThrottlingTier = (QueryThrottlingTier)Enum.Parse(typeof(QueryThrottlingTier), updateElement.Attribute("to").Value);
                            break;
                    }
                manager.CommitQueryClientTypeMapping(item);
            }
        }
        #endregion

        #region Result Sources
        /// <summary>
        /// Update result sources
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="manager"></param>
        /// <param name="rootElement"></param>
        protected void UpdateResultSources(XElement parent, FederationManager manager, string rootElement)
        {
            List<SearchObjectOwner> owners = new List<SearchObjectOwner>();
            foreach (XElement element in parent.XPathSelectElements(rootElement + "/resultSources/deletes/resultSource"))
                manager.RemoveSource(manager.GetSourceByName(element.Attribute("name").Value, SearchObjectOwnerExtension.GetOwner(element, owners)));
            foreach (XElement element in parent.XPathSelectElements(rootElement + "/resultSources/adds/resultSource"))
            {
                Source item = manager.CreateSource(SearchObjectOwnerExtension.GetOwner(element, owners));
                item.Name = element.Attribute("name").Value;
                item.Description = element.Attribute("description").Value;
                item.ProviderId = new Guid(element.Attribute("providerId").Value);
                item.ConnectionUrlTemplate = element.Attribute("url").Value;
                SetResultSourceAuthentication(item, element.Attribute("authenticationType").Value, element.Attribute("accountName").Value);
                SetResultSourceQueryTransform(item, element.Descendants("queryTransform").First(), owners, manager);
                item.Commit();
                if (Convert.ToBoolean(element.Attribute("active").Value))
                    item.Activate();
                else
                    item.Deactivate();
            }
            foreach (XElement element in parent.XPathSelectElements(rootElement + "/resultSources/updates/resultSource"))
            {
                Source item = manager.GetSourceByName(element.Attribute("name").Value, SearchObjectOwnerExtension.GetOwner(element, owners));
                foreach (XElement updateElement in element.XPathSelectElements("updates/update"))
                    switch (updateElement.Attribute("value").Value)
                    {
                        case "description":
                            item.Description = updateElement.Attribute("to").Value;
                            break;
                        case "active":
                            if (Convert.ToBoolean(updateElement.Attribute("to").Value))
                                item.Activate();
                            else
                                item.Deactivate();
                            break;
                        case "providerId":
                            item.ProviderId = new Guid(updateElement.Attribute("to").Value);
                            break;
                        case "url":
                            item.ConnectionUrlTemplate = updateElement.Attribute("to").Value;
                            break;
                        case "authenticationType":
                            SetResultSourceAuthentication(item, updateElement.Attribute("to").Value, element.XPathSelectElement("updates/update[@value='accountName']").Attribute("to").Value);
                            break;
                        case "queryTransform":
                            UpdateQueryTransform(item.QueryTransform, updateElement.Descendants("queryTransform").First(), owners, manager);
                            break;
                    }
                item.Commit();
            }
            UpdateDefaultResultSources(parent, manager, owners, rootElement);
        }

        /// <summary>
        /// Update default result sources
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="manager"></param>
        /// <param name="owners"></param>
        /// <param name="rootElement"></param>
        private void UpdateDefaultResultSources(XElement parent, FederationManager manager, List<SearchObjectOwner> owners, string rootElement)
        {
            foreach (XElement element in parent.XPathSelectElements(rootElement + "/defaultResultSources/updates/defaultResultSource"))
            {
                SearchObjectOwner owner = SearchObjectOwnerExtension.GetOwner(element, owners);
                manager.UpdateDefaultSource(manager.GetSourceByName(element.Attribute("name").Value, owner).Id, owner);
            }
        }

        /// <summary>
        /// Update result source query transform instance
        /// </summary>
        /// <param name="source"></param>
        /// <param name="parent"></param>
        /// <param name="owners"></param>
        /// <param name="manager"></param>
        private void SetResultSourceQueryTransform(Source source, XElement parent, List<SearchObjectOwner> owners, FederationManager manager)
        {
            QueryTransform result = source.CreateQueryTransform(GetQueryTransformOverrideProperties(parent), parent.Attribute("queryTemplate").Value);
            result.SourceId = GetQueryTransformSourceId(parent.Descendants("source").FirstOrDefault(), owners, manager);
            UpdateQueryTransformQueryTemplateParameters(result, parent);
        }

        /// <summary>
        /// Set result source authentication
        /// </summary>
        /// <param name="source"></param>
        /// <param name="authenticationType"></param>
        /// <param name="accountName"></param>
        private void SetResultSourceAuthentication(Source source, string authenticationType, string accountName)
        {
            if (source.HasPermissionToReadAuthInfo && !string.IsNullOrEmpty(authenticationType))
            {
                source.AuthInfo = new AuthenticationInformation((FederationAuthType)Enum.Parse(typeof(FederationAuthType), authenticationType));
                switch (source.AuthInfo.AuthenticationType)
                {
                    case FederationAuthType.Anonymous:
                    case FederationAuthType.LocalNTAuth:
                    case FederationAuthType.PerUserBasicAuth:
                    case FederationAuthType.ApplicationPoolIdentity:
                        break;
                    case FederationAuthType.SingleAccountNTLM:
                    case FederationAuthType.SingleAccountDigest:
                    case FederationAuthType.SingleAccountBasicAuth:
                        source.AuthInfo.Data = new AccountAuthCredentials() { UserName = accountName, SecurePassword = Tools.GetAccountPassword(accountName) };
                        break;
                    case FederationAuthType.SSO:
                        source.AuthInfo.Data = new SsoAuthData() { SsoAppId = accountName };
                        break;
                    default:
                        throw new Exception(string.Format("Result source authentication type '{0}' is not supported.", authenticationType));
                }
            }
        }
        #endregion

        #region Result Types
        /// <summary>
        /// Update result types
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="federationManager"></param>
        /// <param name="resultItemTypeManager"></param>
        /// <param name="rootElement"></param>
        protected void UpdateResultTypes(XElement parent, FederationManager federationManager, ResultItemTypeManager resultItemTypeManager, string rootElement)
        {
            List<SearchObjectOwner> owners = new List<SearchObjectOwner>();
            foreach (XElement element in parent.XPathSelectElements(rootElement + "/resultTypes/deletes/resultType"))
                resultItemTypeManager.DeleteResultItemType(resultItemTypeManager.GetResultItemTypes(SearchObjectOwnerExtension.GetOwner(element, owners), false).Where(i => i.Name == element.Attribute("name").Value).First());
            foreach (XElement element in parent.XPathSelectElements(rootElement + "/resultTypes/adds/resultType"))
            {
                ResultItemType item = new ResultItemType(SearchObjectOwnerExtension.GetOwner(element, owners));
                item.Name = element.Attribute("name").Value;
                item.GetType().GetProperty("OptimizeForFrequentUse").SetValue(item, Convert.ToBoolean(element.Attribute("optimize").Value));
                item.RulePriority = Convert.ToInt32(element.Attribute("priority").Value);
                item.DisplayTemplateUrl = element.Attribute("displayTemplateUrl").Value;
                item.DisplayProperties = element.Attribute("displayProperties").Value;
                item.SourceID = GetResultTypeSource(element.Descendants("resultSource").FirstOrDefault(), owners, federationManager);
                item.Rules = GetResultTypePropertyRules(element.Descendants("rules").FirstOrDefault());
                resultItemTypeManager.AddResultItemType(item);
            }
            foreach (XElement element in parent.XPathSelectElements(rootElement + "/resultTypes/updates/resultType"))
            {
                ResultItemType item = resultItemTypeManager.GetResultItemTypes(SearchObjectOwnerExtension.GetOwner(element, owners), false).Where(i => i.Name == element.Attribute("name").Value).First();
                foreach (XElement updateElement in element.XPathSelectElements("updates/update"))
                    switch (updateElement.Attribute("value").Value)
                    {
                        case "optimize":
                            item.GetType().GetProperty("OptimizeForFrequentUse").SetValue(item, Convert.ToBoolean(updateElement.Attribute("to").Value));
                            break;
                        case "priority":
                            item.RulePriority = Convert.ToInt32(updateElement.Attribute("to").Value);
                            break;
                        case "displayTemplateUrl":
                            item.DisplayTemplateUrl = updateElement.Attribute("to").Value;
                            break;
                        case "displayProperties":
                            item.DisplayProperties = updateElement.Attribute("to").Value;
                            break;
                        case "resultSource":
                            item.SourceID = GetResultTypeSource(updateElement.Descendants("resultSource").FirstOrDefault(), owners, federationManager);
                            break;
                        case "rules":
                            item.Rules = GetResultTypePropertyRules(updateElement.Descendants("rules").FirstOrDefault());
                            break;
                    }
                resultItemTypeManager.UpdateResultItemType(item);
            }
        }

        /// <summary>
        /// Return result type source
        /// </summary>
        /// <param name="element"></param>
        /// <param name="owners"></param>
        /// <param name="manager"></param>
        /// <returns></returns>
        private Guid GetResultTypeSource(XElement element, List<SearchObjectOwner> owners, FederationManager manager)
        {
            return element != null ? manager.GetSourceByName(element.Attribute("name").Value, SearchObjectOwnerExtension.GetOwner(element, owners)).Id : Guid.Empty;
        }

        /// <summary>
        /// Return result type property rules
        /// </summary>
        /// <param name="parent"></param>
        /// <returns></returns>
        private PropertyRuleCollection GetResultTypePropertyRules(XElement parent)
        {
            PropertyRuleCollection rules = new PropertyRuleCollection();
            if (parent != null)
                foreach (XElement element in parent.Descendants("rule"))
                {
                    PropertyRule rule = new PropertyRule(element.Attribute("name").Value, GetResultTypePropertyRuleOperator(element.Attribute("operator").Value));
                    rule.PropertyValues = new List<string>();
                    foreach (XElement value in element.Descendants("value"))
                        rule.PropertyValues.Add(value.Value);
                    rules.Add(rule);
                }
            return rules;
        }

        /// <summary>
        /// Return result type property rule operator
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private PropertyRuleOperator GetResultTypePropertyRuleOperator(string value)
        {
            var defaultOperators = (Dictionary<PropertyRuleOperator.DefaultOperator, PropertyRuleOperator>)typeof(PropertyRuleOperator).GetProperty("DefaultOperators", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
            switch (value)
            {
                case "=":
                    return defaultOperators[PropertyRuleOperator.DefaultOperator.IsEqual];
                case "!=":
                    return defaultOperators[PropertyRuleOperator.DefaultOperator.NotEqual];
                case "Contains":
                    return defaultOperators[PropertyRuleOperator.DefaultOperator.Contains];
                case "!Contains":
                    return defaultOperators[PropertyRuleOperator.DefaultOperator.NotContains];
                case "<":
                    return defaultOperators[PropertyRuleOperator.DefaultOperator.LessThan];
                case ">":
                    return defaultOperators[PropertyRuleOperator.DefaultOperator.GreaterThan];
                case "StartsWith":
                    return defaultOperators[PropertyRuleOperator.DefaultOperator.StartsWith];
                default:
                    throw new Exception(string.Format("Property rule operator '{0}' is not supported.", value));
            }
        }
        #endregion

        #region Query Rules
        /// <summary>
        /// Update query rules
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="application"></param>
        /// <param name="federationManager"></param>
        protected void UpdateQueryRules(XElement parent, FederationManager federationManager, QueryRuleManager queryRuleManager, string rootElement)
        {
            List<SearchObjectOwner> owners = new List<SearchObjectOwner>();
            foreach (XElement element in parent.XPathSelectElements(rootElement + "/queryRules/deletes/queryRule"))
            {
                QueryRuleCollection rules = queryRuleManager.GetQueryRules(new SearchObjectFilter(SearchObjectOwnerExtension.GetOwner(element, owners)) { IncludeHigherLevel = false, IncludeLowerLevels = false });
                rules.RemoveQueryRule(rules.Where(i => i.DisplayName == element.Attribute("name").Value).First());
            }
            foreach (XElement element in parent.XPathSelectElements(rootElement + "/queryRules/adds/queryRule"))
            {
                SearchObjectFilter filter = new SearchObjectFilter(SearchObjectOwnerExtension.GetOwner(element, owners)) { IncludeHigherLevel = false, IncludeLowerLevels = false };
                QueryRule item = queryRuleManager.GetQueryRules(filter).CreateQueryRule(element.Attribute("name").Value, Tools.GetDateValueUTC(element.Attribute("startDate").Value), Tools.GetDateValueUTC(element.Attribute("endDate").Value), Convert.ToBoolean(element.Attribute("active").Value));
                item.ReviewDate = Tools.GetDateValueUTC(element.Attribute("reviewDate").Value);
                item.Contact = element.Attribute("contact").Value;
                UpdateQueryRuleContextConditions(item, element.Descendants("contextConditions").FirstOrDefault(), owners, queryRuleManager, federationManager);
                UpdateQueryRuleQueryConditions(item, element.Descendants("queryConditions").FirstOrDefault(), federationManager);
                UpdateQueryRuleBestBetsAction(item, element.Descendants("bestBetActions").FirstOrDefault(), queryRuleManager);
                UpdateQueryRuleResultBlockActions(item, element.Descendants("queryActions").FirstOrDefault(), owners, federationManager);
                UpdateQueryRuleChangeQueryAction(item, element.Descendants("changeQueryAction").FirstOrDefault(), owners, federationManager);
                item.Update();
                UpdateQueryRuleGroup(item, element.Attribute("group").Value, (GroupProcessingDirective)Enum.Parse(typeof(GroupProcessingDirective), element.Attribute("processing").Value), queryRuleManager, filter);
            }
            foreach (XElement element in parent.XPathSelectElements(rootElement + "/queryRules/updates/queryRule"))
            {
                SearchObjectFilter filter = new SearchObjectFilter(SearchObjectOwnerExtension.GetOwner(element, owners)) { IncludeHigherLevel = false, IncludeLowerLevels = false };
                QueryRule item = queryRuleManager.GetQueryRules(filter)[element.Attribute("name").Value].First();
                foreach (XElement updateElement in element.XPathSelectElements("updates/update"))
                    switch (updateElement.Attribute("value").Value)
                    {
                        case "active":
                            item.IsActive = Convert.ToBoolean(updateElement.Attribute("to").Value);
                            break;
                        case "startDate":
                            item.StartDate = Tools.GetDateValueUTC(updateElement.Attribute("to").Value);
                            break;
                        case "endDate":
                            item.EndDate = Tools.GetDateValueUTC(updateElement.Attribute("to").Value);
                            break;
                        case "reviewDate":
                            item.ReviewDate = Tools.GetDateValueUTC(updateElement.Attribute("to").Value);
                            break;
                        case "contact":
                            item.Contact = updateElement.Attribute("to").Value;
                            break;
                        case "group":
                            UpdateQueryRuleGroup(item, element.Attribute("to").Value, GroupProcessingDirective.Stop, queryRuleManager, filter);
                            break;
                        case "contextConditions":
                            UpdateQueryRuleContextConditions(item, element.Descendants("contextConditions").FirstOrDefault(), owners, queryRuleManager, federationManager);
                            break;
                        case "queryConditions":
                            UpdateQueryRuleQueryConditions(item, element.Descendants("queryConditions").FirstOrDefault(), federationManager);
                            break;
                        case "bestBetActions":
                            UpdateQueryRuleBestBetsAction(item, element.Descendants("bestBetActions").FirstOrDefault(), queryRuleManager);
                            break;
                        case "queryActions":
                            UpdateQueryRuleResultBlockActions(item, element.Descendants("queryActions").FirstOrDefault(), owners, federationManager);
                            break;
                        case "changeQueryAction":
                            UpdateQueryRuleChangeQueryAction(item, element.Descendants("changeQueryAction").FirstOrDefault(), owners, federationManager);
                            break;
                    }
                item.Update();
            }
        }

        /// <summary>
        /// Update query rule group
        /// </summary>
        /// <param name="item"></param>
        /// <param name="name"></param>
        /// <param name="processingDirective"></param>
        /// <param name="manager"></param>
        /// <param name="filter"></param>
        private void UpdateQueryRuleGroup(QueryRule item, string name, GroupProcessingDirective processingDirective, QueryRuleManager manager, SearchObjectFilter filter)
        {
            if (!string.IsNullOrEmpty(name))
                item.MoveToGroup(manager.GetQueryRuleGroups(filter)[name].First(), processingDirective);
        }

        /// <summary>
        /// Update query rule context conditions
        /// </summary>
        /// <param name="item"></param>
        /// <param name="parent"></param>
        /// <param name="owners"></param>
        /// <param name="manager"></param>
        /// <param name="federationManager"></param>
        private void UpdateQueryRuleContextConditions(QueryRule item, XElement parent, List<SearchObjectOwner> owners, QueryRuleManager manager, FederationManager federationManager)
        {
            item.ContextConditions.Clear();
            if (parent != null)
                foreach (XElement element in parent.Descendants("contextCondition"))
                    if (element.Attribute("type").Value == "Source")
                        item.CreateSourceContextCondition(federationManager.GetSourceByName(element.Attribute("name").Value, SearchObjectOwnerExtension.GetOwner(element, owners)));
                    else if (element.Attribute("type").Value == "Category")
                    {
                        TaxonomySession session = new TaxonomySession(SPAdministrationWebApplication.Local.Sites[0]);
                        Guid termStoreId = new Guid(element.Attribute("termStoreId").Value);
                        Guid termSetId = new Guid(element.Attribute("termSetId").Value);
                        Guid termId = new Guid(element.Attribute("termId").Value);
                        TermStore termStore = termStoreId != Guid.Empty ? session.TermStores[termStoreId] : session.DefaultKeywordsTermStore;
                        if (termId == Guid.Empty)
                            item.CreateCategoryContextCondition(termStore.GetTermSet(termSetId));
                        else
                            item.CreateCategoryContextCondition(termStore.GetTerm(termSetId, termId));
                    }
                    else if (element.Attribute("type").Value == "UserSegment")
                        item.CreateUserSegmentContextCondition(GetCreateUpdateQueryRuleUserSegment(element, owners, manager));
                    else if (element.Attribute("type").Value == "Generic")
                        item.CreateGenericContextCondition(element.Attribute("expression").Value);
                    else
                        throw new Exception(string.Format("Query rule context condition type '{0}' is not supported.", element.Attribute("type").Value));
        }

        /// <summary>
        /// Return query rule user segment (existing, create new, update existing)
        /// </summary>
        /// <param name="element"></param>
        /// <param name="owners"></param>
        /// <param name="manager"></param>
        /// <returns></returns>
        private UserSegment GetCreateUpdateQueryRuleUserSegment(XElement element, List<SearchObjectOwner> owners, QueryRuleManager manager)
        {
            UserSegmentCollection userSegments = manager.GetUserSegments(new SearchObjectFilter(SearchObjectOwnerExtension.GetOwner(element, owners)) { IncludeHigherLevel = false, IncludeLowerLevels = false });
            UserSegment userSegment = userSegments.Where(i => i.DisplayName == element.Attribute("name").Value).FirstOrDefault();
            if (userSegment == null)
                userSegment = userSegments.CreateUserSegment(element.Attribute("name").Value, string.Empty);
            if (GetQueryRuleUserSegmentTermsText(userSegment.Terms) != GetQueryRuleUserSegmentTermsText(element.Descendants("terms").FirstOrDefault()))
            {
                userSegment.Terms.Clear();
                foreach (XElement value in element.Descendants("term"))
                    userSegment.Terms.Add(new Guid(value.Value));
                userSegment.Update();
            }
            return userSegment;
        }

        /// <summary>
        /// Return query rule user segment terms text
        /// </summary>
        /// <param name="terms"></param>
        /// <returns></returns>
        private string GetQueryRuleUserSegmentTermsText(UserSegmentTermCollection terms)
        {
            StringBuilder text = new StringBuilder();
            if (terms != null)
                foreach (Guid value in terms)
                    text.AppendFormat("{0},", value.ToString());
            return text.ToString();
        }

        /// <summary>
        /// Return query rule user segment terms text
        /// </summary>
        /// <param name="terms"></param>
        /// <returns></returns>
        private string GetQueryRuleUserSegmentTermsText(XElement terms)
        {
            StringBuilder text = new StringBuilder();
            if (terms != null)
                foreach (XElement value in terms.Descendants("term"))
                    text.AppendFormat("{0},", value.Value);
            return text.ToString();
        }

        /// <summary>
        /// Update query rule query conditions
        /// </summary>
        /// <param name="item"></param>
        /// <param name="parent"></param>
        /// <param name="manager"></param>
        private void UpdateQueryRuleQueryConditions(QueryRule item, XElement parent, FederationManager manager)
        {
            item.QueryConditions.Clear();
            if (parent != null)
                foreach (XElement element in parent.Descendants("queryCondition"))
                {
                    QueryCondition condition;
                    if (element.Attribute("type").Value == "Keyword")
                        condition = item.QueryConditions.CreateKeywordCondition(GetQueryRuleQueryConditionValues(element.Descendants("term")), true);
                    else if (element.Attribute("type").Value == "PeopleName")
                        condition = item.QueryConditions.CreatePeopleNameCondition(true);
                    else if (element.Attribute("type").Value == "CommonQuery")
                    {
                        condition = item.QueryConditions.CreateCommonQueryCondition(manager.GetSourceByName(element.Attribute("source").Value, item.Owner).Id, true);
                        ((CommonQueryCondition)condition).Threshold = Convert.ToDouble(element.Attribute("threshold").Value);
                        ((CommonQueryCondition)condition).Additions = GetQueryRuleQueryConditionValues(element.Descendants("addition"));
                    }
                    else if (element.Attribute("type").Value == "CommonlyClickedProperty")
                    {
                        condition = item.QueryConditions.CreateCommonlyClickedPropertyCondition(element.Attribute("property").Value, GetQueryRuleQueryConditionValues(element.Descendants("value")), true);
                        ((CommonlyClickedPropertyCondition)condition).Threshold = Convert.ToDouble(element.Attribute("threshold").Value);
                    }
                    else if (element.Attribute("type").Value == "RegularExpression")
                        condition = item.QueryConditions.CreateRegularExpressionCondition(element.Attribute("pattern").Value, true);
                    else if (element.Attribute("type").Value == "TaxonomyDictionary")
                    {
                        TaxonomySession session = new TaxonomySession(SPAdministrationWebApplication.Local.Sites[0]);
                        Guid termStoreId = new Guid(element.Attribute("termStoreId").Value);
                        Guid termSetId = new Guid(element.Attribute("termSetId").Value);
                        Guid termId = new Guid(element.Attribute("termId").Value);
                        TermStore termStore = termStoreId != Guid.Empty ? session.TermStores[termStoreId] : session.DefaultKeywordsTermStore;
                        if (termId == Guid.Empty)
                            condition = item.QueryConditions.CreateTaxonomyDictionaryCondition(element.Attribute("name").Value, termStore.GetTermSet(termSetId), true);
                        else
                            condition = item.QueryConditions.CreateTaxonomyDictionaryCondition(element.Attribute("name").Value, termStore.GetTerm(termSetId, termId), true);
                    }
                    else
                        throw new Exception(string.Format("Query rule condition type '{0}' is not supported.", element.Attribute("type").Value));
                    condition.MatchingOptions = (MatchingOptions)Enum.Parse(typeof(MatchingOptions), element.Attribute("match").Value);
                    try
                    {
                        condition.SubjectTermsOrigin = (BoundVariableOrigin)Enum.Parse(typeof(BoundVariableOrigin), element.Attribute("terms").Value);
                    }
                    catch { }
                }
        }

        /// <summary>
        /// Return query rule keyword condition terms
        /// </summary>
        /// <param name="parent"></param>
        /// <returns></returns>
        private List<string> GetQueryRuleQueryConditionValues(IEnumerable<XElement> elements)
        {
            List<string> result = new List<string>();
            if (elements != null)
                foreach (XElement value in elements)
                    result.Add(value.Value);
            return result;
        }

        /// <summary>
        /// Update query rule best bets action
        /// </summary>
        /// <param name="item"></param>
        /// <param name="parent"></param>
        /// <param name="manager"></param>
        private void UpdateQueryRuleBestBetsAction(QueryRule item, XElement parent, QueryRuleManager manager)
        {
            if (item.AssignBestBetsAction != null)
                item.RemoveQueryAction(item.AssignBestBetsAction);
            if (parent != null)
            {
                AssignBestBetsAction action = (AssignBestBetsAction)item.CreateQueryAction(QueryActionType.AssignBestBet);
                foreach (XElement element in parent.Descendants("bestBetAction"))
                    action.BestBetIds.Add(GetCreateUpdateQueryRuleBestBet(element, item.Owner, manager).Id);
            }
        }

        /// <summary>
        /// Return query rule best bet (existing, create new, update existing)
        /// </summary>
        /// <param name="element"></param>
        /// <param name="owner"></param>
        /// <param name="manager"></param>
        /// <returns></returns>
        private Microsoft.Office.Server.Search.Query.Rules.BestBet GetCreateUpdateQueryRuleBestBet(XElement element, SearchObjectOwner owner, QueryRuleManager manager)
        {
            Microsoft.Office.Server.Search.Query.Rules.BestBetCollection bestBets = manager.GetBestBets(new SearchObjectFilter(owner) { IncludeHigherLevel = false, IncludeLowerLevels = false });
            Microsoft.Office.Server.Search.Query.Rules.BestBet bestBet = bestBets.Where(i => i.Title == element.Attribute("title").Value).FirstOrDefault();
            if (bestBet == null)
                bestBet = bestBets.CreateBestBet(element.Attribute("title").Value, new Uri(element.Attribute("url").Value), element.Attribute("description").Value, Convert.ToBoolean(element.Attribute("visualBestBet").Value));
            bool update = false;
            if (bestBet.Url.ToString() != element.Attribute("url").Value)
            {
                bestBet.Url = new Uri(element.Attribute("url").Value);
                update = true;
            }
            if (bestBet.Description != element.Attribute("description").Value)
            {
                bestBet.Description = element.Attribute("description").Value;
                update = true;
            }
            if (bestBet.IsVisualBestBet != Convert.ToBoolean(element.Attribute("visualBestBet").Value))
            {
                bestBet.IsVisualBestBet = Convert.ToBoolean(element.Attribute("visualBestBet").Value);
                update = true;
            }
            if (update)
                bestBet.Update();
            return bestBet;
        }

        /// <summary>
        /// Update query rule result block actions
        /// </summary>
        /// <param name="item"></param>
        /// <param name="parent"></param>
        /// <param name="owners"></param>
        /// <param name="manager"></param>
        private void UpdateQueryRuleResultBlockActions(QueryRule item, XElement parent, List<SearchObjectOwner> owners, FederationManager manager)
        {
            if (item.CreateResultBlockActions != null)
                while (item.CreateResultBlockActions.Count > 0)
                    item.RemoveQueryAction(item.CreateResultBlockActions[0]);
            if (parent != null)
                foreach (XElement element in parent.Descendants("queryAction"))
                {
                    CreateResultBlockAction action = (CreateResultBlockAction)item.CreateQueryAction(QueryActionType.CreateResultBlock);
                    action.ResultTitle.DefaultLanguageString = element.Attribute("title").Value;
                    action.AlwaysShow = Convert.ToBoolean(element.Attribute("alwaysShow").Value);
                    action.ResultTitleUrl = element.Attribute("resultTitleUrl").Value;
                    if (!string.IsNullOrEmpty(element.Attribute("groupTemplate").Value))
                        action.GroupTemplateId = element.Attribute("groupTemplate").Value;
                    if (!string.IsNullOrEmpty(element.Attribute("itemTemplate").Value))
                        action.ItemTemplateId = element.Attribute("itemTemplate").Value;
                    UpdateQueryTransform(action.QueryTransform, element.Descendants("queryTransform").FirstOrDefault(), owners, manager);
                }
        }

        /// <summary>
        /// Update query rule change query action
        /// </summary>
        /// <param name="item"></param>
        /// <param name="element"></param>
        /// <param name="owners"></param>
        /// <param name="manager"></param>
        private void UpdateQueryRuleChangeQueryAction(QueryRule item, XElement element, List<SearchObjectOwner> owners, FederationManager manager)
        {
            if (item.ChangeQueryAction != null)
                item.RemoveQueryAction(item.ChangeQueryAction);
            if (element != null)
            {
                ChangeQueryAction action = (ChangeQueryAction)item.CreateQueryAction(QueryActionType.ChangeQuery);
                UpdateQueryTransform(action.QueryTransform, element.Descendants("queryTransform").FirstOrDefault(), owners, manager);
            }
        }

        /// <summary>
        /// Update query transform
        /// </summary>
        /// <param name="queryTransform"></param>
        /// <param name="parent"></param>
        /// <param name="owners"></param>
        /// <param name="manager"></param>
        private void UpdateQueryTransform(QueryTransform queryTransform, XElement parent, List<SearchObjectOwner> owners, FederationManager manager)
        {
            if (parent != null)
            {
                queryTransform.QueryTemplate = parent.Attribute("queryTemplate") != null ? parent.Attribute("queryTemplate").Value : null;
                queryTransform.SourceId = GetQueryTransformSourceId(parent.Descendants("source").FirstOrDefault(), owners, manager);
                queryTransform.OverrideProperties = GetQueryTransformOverrideProperties(parent.Descendants("overrideProperties").FirstOrDefault());
                UpdateQueryTransformQueryTemplateParameters(queryTransform, parent.Descendants("queryTemplateParameters").FirstOrDefault());
            }
        }

        /// <summary>
        /// Return query transform source id
        /// </summary>
        /// <param name="element"></param>
        /// <param name="owners"></param>
        /// <param name="manager"></param>
        /// <returns></returns>
        private Guid? GetQueryTransformSourceId(XElement element, List<SearchObjectOwner> owners, FederationManager manager)
        {
            if (element != null)
                return manager.GetSourceByName(element.Attribute("name").Value, SearchObjectOwnerExtension.GetOwner(element, owners)).Id;
            return null;
        }

        /// <summary>
        /// Return query transform override properties
        /// </summary>
        /// <param name="parent"></param>
        /// <returns></returns>
        private QueryTransformProperties GetQueryTransformOverrideProperties(XElement parent)
        {
            QueryTransformProperties overrideProperties = null;
            if (parent != null)
            {
                overrideProperties = new QueryTransformProperties();
                foreach (XElement element in parent.Descendants("overrideProperty"))
                    overrideProperties[element.Attribute("key").Value] = GetQueryTransformOverridePropertyValue(element);
            }
            return overrideProperties;
        }

        /// <summary>
        /// Return query transform override property value
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        private object GetQueryTransformOverridePropertyValue(XElement element)
        {
            string type = element.Attribute("type").Value;
            if (type == "Int32")
                return Convert.ToInt32(element.Attribute("value").Value);
            if (type == "String")
                return element.Attribute("value").Value;
            else if (type == "StringCollection")
            {
                StringCollection result = new StringCollection();
                foreach (XElement item in element.Descendants("value"))
                    result.Add(item.Value);
                return result;
            }
            else if (type == "SortCollection")
            {
                SortCollection result = new SortCollection();
                foreach (XElement item in element.Descendants("value"))
                    result.Add(item.Attribute("property").Value, (SortDirection)Enum.Parse(typeof(SortDirection), item.Attribute("direction").Value));
                return result;
            }
            else if (type == "ReorderingRuleCollection")
            {
                ReorderingRuleCollection result = new ReorderingRuleCollection();
                foreach (XElement item in element.Descendants("value"))
                    result.Add((ReorderingRuleMatchType)Enum.Parse(typeof(ReorderingRuleMatchType), item.Attribute("matchType").Value), item.Attribute("matchValue").Value, Convert.ToInt32(item.Attribute("boost").Value));
                return result;
            }
            throw new Exception(string.Format("Query transform value type '{0}' is not supported.", type));
        }

        /// <summary>
        /// Update query transform query template parameters
        /// </summary>
        /// <param name="queryTransform"></param>
        /// <param name="parent"></param>
        private void UpdateQueryTransformQueryTemplateParameters(QueryTransform queryTransform, XElement parent)
        {
            if (parent != null)
                foreach (XElement element in parent.Descendants("queryTemplateParameter"))
                    queryTransform.QueryTemplateParameters[element.Attribute("key").Value] = Convert.ToBoolean(element.Attribute("value").Value);
            else
                queryTransform.QueryTemplateParameters.Clear();
        }
        #endregion

        #region Managed Properties
        /// <summary>
        /// Update managed properties
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="application"></param>
        private void UpdateManagedProperties(XElement parent, SearchServiceApplication application)
        {
            List<SearchObjectOwner> owners = new List<SearchObjectOwner>();
            QueryRuleManager manager = new QueryRuleManager(application);
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/managedProperties/deletes/managedProperty"))
            {
                SearchObjectOwner owner = SearchObjectOwnerExtension.GetOwner(element, owners);
                application.DeleteManagedProperty(application.GetManagedProperties(null, int.MaxValue, owner).Where(i => i.Name == element.Attribute("name").Value).First(), owner);
            }
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/managedProperties/adds/managedProperty"))
            {
                SearchObjectOwner owner = SearchObjectOwnerExtension.GetOwner(element, owners);
                ManagedPropertyInfo item = application.CreateManagedProperty(element.Attribute("name").Value, (ManagedDataType)Enum.Parse(typeof(ManagedDataType), element.Attribute("type").Value), owner);
                UpdateManagedPropertyCreate(element, item);
                application.UpdateManagedProperty(item, owner);
                UpdateManagedPropertyAliases(item, element.Descendants("aliases").FirstOrDefault(), application, owner);
                UpdateManagedPropertyMappedCrawledProperties(item, element.Descendants("crawledProperties").FirstOrDefault(), application, owner);
            }
            foreach (XElement element in parent.XPathSelectElements("searchServiceApplication/managedProperties/updates/managedProperty"))
            {
                SearchObjectOwner owner = SearchObjectOwnerExtension.GetOwner(element, owners);
                ManagedPropertyInfo item = application.GetManagedProperty(element.Attribute("name").Value, owner);
                UpdateManagedPropertyUpdate(element, item);
                foreach (XElement updateElement in element.XPathSelectElements("updates/update"))
                    switch (updateElement.Attribute("value").Value)
                    {
                        case "aliases":
                            UpdateManagedPropertyAliases(item, element.Descendants("aliases").FirstOrDefault(), application, owner);
                            break;
                        case "crawledProperties":
                            UpdateManagedPropertyMappedCrawledProperties(item, element.Descendants("crawledProperties").FirstOrDefault(), application, owner);
                            break;
                    }
                application.UpdateManagedProperty(item, owner);
            }
        }

        /// <summary>
        /// Update managed property attributes
        /// </summary>
        /// <param name="element"></param>
        /// <param name="item"></param>
        protected void UpdateManagedPropertyCreate(XElement element, ManagedPropertyInfo item)
        {
            if (!string.IsNullOrEmpty(element.Attribute("description").Value))
                item.Description = element.Attribute("description").Value;
            item.Searchable = Convert.ToBoolean(element.Attribute("searchable").Value);
            item.Queryable = Convert.ToBoolean(element.Attribute("queryable").Value);
            item.Retrievable = Convert.ToBoolean(element.Attribute("retrievable").Value);
            item.HasMultipleValues = Convert.ToBoolean(element.Attribute("multipleValues").Value);
            item.Refinable = Convert.ToBoolean(element.Attribute("refinable").Value);
            item.Sortable = Convert.ToBoolean(element.Attribute("sortable").Value);
            item.SortableType = (SortableType)Enum.Parse(typeof(SortableType), element.Attribute("sortableType").Value);
            item.SafeForAnonymous = Convert.ToBoolean(element.Attribute("safeForAnonymous").Value);
            item.TokenNormalization = Convert.ToBoolean(element.Attribute("tokenNormalization").Value);
            item.CompleteMatching = Convert.ToBoolean(element.Attribute("completeMatching").Value);
            item.CompanyExtraction = Convert.ToBoolean(element.Attribute("companyExtraction").Value);
            item.WordExactExtractionCustom = Convert.ToBoolean(element.Attribute("wordExactExtractionCustom").Value);
            item.WordExtractionCustom1 = Convert.ToBoolean(element.Attribute("wordExtractionCustom1").Value);
            item.WordExtractionCustom2 = Convert.ToBoolean(element.Attribute("wordExtractionCustom2").Value);
            item.WordExtractionCustom3 = Convert.ToBoolean(element.Attribute("wordExtractionCustom3").Value);
            item.WordExtractionCustom4 = Convert.ToBoolean(element.Attribute("wordExtractionCustom4").Value);
            item.WordExtractionCustom5 = Convert.ToBoolean(element.Attribute("wordExtractionCustom5").Value);
            item.WordPartExactExtractionCustom = Convert.ToBoolean(element.Attribute("wordPartExactExtractionCustom").Value);
            item.WordPartExtractionCustom1 = Convert.ToBoolean(element.Attribute("wordPartExtractionCustom1").Value);
            item.WordPartExtractionCustom2 = Convert.ToBoolean(element.Attribute("wordPartExtractionCustom2").Value);
            item.WordPartExtractionCustom3 = Convert.ToBoolean(element.Attribute("wordPartExtractionCustom3").Value);
            item.WordPartExtractionCustom4 = Convert.ToBoolean(element.Attribute("wordPartExtractionCustom4").Value);
            item.WordPartExtractionCustom5 = Convert.ToBoolean(element.Attribute("wordPartExtractionCustom5").Value);
            item.Context = Convert.ToInt16(element.Attribute("context").Value);
            item.DeleteDisallowed = Convert.ToBoolean(element.Attribute("deleteDisallowed").Value);
            item.EnabledForScoping = Convert.ToBoolean(element.Attribute("enabledForScoping").Value);
            item.EntityExtractorBitMap = Convert.ToInt32(element.Attribute("entityExtractorBitMap").Value);
            if (!string.IsNullOrEmpty(element.Attribute("fullTextIndex").Value))
                item.FullTextIndex = element.Attribute("fullTextIndex").Value;
            item.IndexOptions = Convert.ToInt32(element.Attribute("indexOptions").Value);
            item.MappingDisallowed = Convert.ToBoolean(element.Attribute("mappingDisallowed").Value);
            item.RemoveDuplicates = Convert.ToBoolean(element.Attribute("removeDuplicates").Value);
            item.RespectPriority = Convert.ToBoolean(element.Attribute("respectPriority").Value);
            if (!string.IsNullOrEmpty(element.Attribute("updateGroup").Value))
                item.UpdateGroup = element.Attribute("updateGroup").Value;
            UpdateManagedPropertyRefinerConfiguration(item, element.Descendants("refinerConfiguration").FirstOrDefault());
        }

        /// <summary>
        /// Update managed property attributes
        /// </summary>
        /// <param name="element"></param>
        /// <param name="item"></param>
        protected void UpdateManagedPropertyUpdate(XElement element, ManagedPropertyInfo item)
        {
            foreach (XElement updateElement in element.XPathSelectElements("updates/update"))
                switch (updateElement.Attribute("value").Value)
                {
                    case "description":
                        item.Description = updateElement.Attribute("to").Value;
                        break;
                    case "type":
                        item.ManagedType = (ManagedDataType)Enum.Parse(typeof(ManagedDataType), updateElement.Attribute("to").Value);
                        break;
                    case "searchable":
                        item.Searchable = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "queryable":
                        item.Queryable = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "retrievable":
                        item.Retrievable = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "multipleValues":
                        item.HasMultipleValues = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "refinable":
                        item.Refinable = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "sortable":
                        item.Sortable = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "sortableType":
                        item.SortableType = (SortableType)Enum.Parse(typeof(SortableType), updateElement.Attribute("to").Value);
                        break;
                    case "safeForAnonymous":
                        item.SafeForAnonymous = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "tokenNormalization":
                        item.TokenNormalization = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "completeMatching":
                        item.CompleteMatching = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "companyExtraction":
                        item.CompanyExtraction = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "wordExactExtractionCustom":
                        item.WordExactExtractionCustom = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "wordExtractionCustom1":
                        item.WordExtractionCustom1 = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "wordExtractionCustom2":
                        item.WordExtractionCustom2 = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "wordExtractionCustom3":
                        item.WordExtractionCustom3 = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "wordExtractionCustom4":
                        item.WordExtractionCustom4 = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "wordExtractionCustom5":
                        item.WordExtractionCustom5 = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "wordPartExactExtractionCustom":
                        item.WordPartExactExtractionCustom = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "wordPartExtractionCustom1":
                        item.WordPartExtractionCustom1 = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "wordPartExtractionCustom2":
                        item.WordPartExtractionCustom2 = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "wordPartExtractionCustom3":
                        item.WordPartExtractionCustom3 = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "wordPartExtractionCustom4":
                        item.WordPartExtractionCustom4 = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "wordPartExtractionCustom5":
                        item.WordPartExtractionCustom5 = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "context":
                        item.Context = Convert.ToInt16(updateElement.Attribute("to").Value);
                        break;
                    case "deleteDisallowed":
                        item.DeleteDisallowed = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "enabledForScoping":
                        item.EnabledForScoping = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "entityExtractorBitMap":
                        item.EntityExtractorBitMap = Convert.ToInt32(updateElement.Attribute("to").Value);
                        break;
                    case "fullTextIndex":
                        item.FullTextIndex = updateElement.Attribute("to").Value;
                        break;
                    case "indexOptions":
                        item.IndexOptions = Convert.ToInt32(updateElement.Attribute("to").Value);
                        break;
                    case "mappingDisallowed":
                        item.MappingDisallowed = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "removeDuplicates":
                        item.RemoveDuplicates = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "respectPriority":
                        item.RespectPriority = Convert.ToBoolean(updateElement.Attribute("to").Value);
                        break;
                    case "updateGroup":
                        item.UpdateGroup = updateElement.Attribute("to").Value;
                        break;
                    case "refinerConfiguration":
                        UpdateManagedPropertyRefinerConfiguration(item, element.Descendants("refinerConfiguration").FirstOrDefault());
                        break;
                }
        }

        /// <summary>
        /// Update managed property refiner configuration
        /// </summary>
        /// <param name="item"></param>
        /// <param name="parent"></param>
        private void UpdateManagedPropertyRefinerConfiguration(ManagedPropertyInfo item, XElement parent)
        {
            if (parent != null)
            {
                if (item.RefinerConfiguration == null)
                    item.RefinerConfiguration = new RefinerConfiguration();
                item.RefinerConfiguration.Type = (RefinerType)Enum.Parse(typeof(RefinerType), parent.Attribute("type").Value);
                item.RefinerConfiguration.Anchoring = (RefinerAnchoring)Enum.Parse(typeof(RefinerAnchoring), parent.Attribute("anchoring").Value);
                item.RefinerConfiguration.CutoffMaxBuckets = Convert.ToInt32(parent.Attribute("cutoffMaxBuckets").Value);
                item.RefinerConfiguration.Divisor = Convert.ToInt32(parent.Attribute("divisor").Value);
                item.RefinerConfiguration.Intervals = Convert.ToInt32(parent.Attribute("intervals").Value);
                item.RefinerConfiguration.Resolution = Convert.ToInt32(parent.Attribute("resolution").Value);
            }
        }

        /// <summary>
        /// Update managed property aliases
        /// </summary>
        /// <param name="item"></param>
        /// <param name="parent"></param>
        /// <param name="application"></param>
        /// <param name="owner"></param>
        private void UpdateManagedPropertyAliases(ManagedPropertyInfo item, XElement parent, SearchServiceApplication application, SearchObjectOwner owner)
        {
            List<string> aliases = new List<string>();
            if (parent != null)
                foreach (XElement element in parent.Descendants("alias"))
                    aliases.Add(element.Value);
            application.SetManagedPropertyAliases(item, aliases.ToArray(), owner);
        }

        /// <summary>
        /// Update managed property mapped crawled properties
        /// </summary>
        /// <param name="item"></param>
        /// <param name="parent"></param>
        /// <param name="application"></param>
        /// <param name="owner"></param>
        private void UpdateManagedPropertyMappedCrawledProperties(ManagedPropertyInfo item, XElement parent, SearchServiceApplication application, SearchObjectOwner owner)
        {
            int order = 1;
            List<MappingInfo> mappings = new List<MappingInfo>();
            if (parent != null)
                foreach (XElement element in parent.Descendants("crawledProperty"))
                {
                    CrawledPropertyInfo crawledProperty = application.GetAllCrawledProperties(element.Attribute("name").Value, element.Attribute("category").Value, 0, owner).FirstOrDefault();
                    if (crawledProperty != null)
                        mappings.Add(new MappingInfo() { CrawledPropertyName = crawledProperty.Name, CrawledPropset = crawledProperty.Propset, SchemaId = crawledProperty.SchemaId, MappingOrder = order++, ManagedPid = item.Pid });
                    else
                        controller.OnProgressMessage(MessageType.Warning, string.Format("Crawled property '{0}:{1}' is not available. Mapping managed property skipped.", element.Attribute("name").Value, element.Attribute("category").Value));
                }
            application.SetManagedPropertyMappings(item, mappings.ToArray(), owner);
        }
        #endregion

    }
}